using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Visualizator.Physics;
using System.Reflection;
using GASS.CUDA.Direct3D;
using GASS.CUDA;
using GASS.CUDA.Types;

namespace Visualizator
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Visualizator : Game
    {
        public GraphicsDeviceManager Graphics;
        public GraphicsDevice Device;
        public SpriteBatch SpriteBatch;
        public BasicEffect Effect;
        public Random Random;
        public Camera Camera;

        public static IntPtr d3d_VertexBuffer;
        public static IntPtr d3d_Device;
        public static CUDA Cuda;


        /// <summary>
        /// El sistema a simular.
        /// </summary>
        public MassSpringSystem MSSystem;

        /// <summary>
        /// El integrador a usar.
        /// </summary>
        public IVerletIntegrator Integrator;

        /// <summary>
        /// Usamos un arreglo de CUDAVertexPositionColor
        /// pero una posible optimizacion es utilizar directamente
        /// vertices en la tarjeta de video (CUDA-DirectX9 interop)
        /// en el algoritmo de integracion.
        /// </summary>
        public CUDAVertexPositionColor[] Vertices;


        #region Constructor
        public Visualizator()
        {
            Graphics = new GraphicsDeviceManager(this);
            Graphics.IsFullScreen = false;
            Graphics.PreferredBackBufferWidth = Settings.ViewportSize.X;
            Graphics.PreferredBackBufferHeight = Settings.ViewportSize.Y;
            Graphics.SynchronizeWithVerticalRetrace = false;
            this.IsFixedTimeStep = false;
            Content.RootDirectory = "Content";

            Cuda = new CUDA(0, true);
        } 
        #endregion

        #region Initialize
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// TODO.
        /// </summary>
        protected override void Initialize()
        {
            Device = Graphics.GraphicsDevice;
            Effect = new BasicEffect(Device, null);
            Vertices = new CUDAVertexPositionColor[Settings.ParticleCount];

            // Inicializamos el generador de numeros aleatorios con una 
            // semilla fija para tener cierto control del estado inicial.
            Random = new Random(0);

            // Inicializamos la camara.
            Camera = new Camera(this);
            Components.Add(Camera);

            // Inicializamos el sistema
            MSSystem = new MassSpringSystem(Settings.ParticleCount, 0);
            for (int i = 0; i < Settings.ParticleCount; i++)
            {
                MSSystem.SetPos(i, Util.ToF4(new Vector4(Random.NextVector3(Settings.WorldBox.Min, Settings.WorldBox.Max), 0)));
                MSSystem.SetPrevPos(i, Util.AddF4(MSSystem.GetPos(i), Util.ToF4(new Vector4(Random.NextVector3(-Vector3.One, Vector3.One), 0))));
            }

            // Aqui usamos refleccion para conseguir el apuntador al
            // VertexBuffer en la memoria de video, que XNA mantiene privado.

            unsafe
            {
                Pointer d_device = ReflectionHelper.GetPrivateFieldByName<Pointer>(Device, "pComPtr");
                d3d_Device = new IntPtr(Pointer.Unbox(d_device));
            }
            //cudaError error = CUD3DRuntime.cudaD3D9SetDirect3DDevice(Visualizator.d3d_Device);

            unsafe
            {
                VertexBuffer buffer = new VertexBuffer(Device, Settings.ParticleCount * 4, BufferUsage.None);
                Pointer d_vertexBuffer = ReflectionHelper.GetPrivateFieldByName<Pointer>(buffer, "pComPtr");
                d3d_VertexBuffer = new IntPtr(Pointer.Unbox(d_vertexBuffer));
            }

            // Inicializamos el integrador a usar.
            Integrator = new GPUVerletIntegrator();

            Integrator.ParticleCount = Settings.ParticleCount;
            Integrator.Pos = MSSystem.Pos;
            Integrator.PrevPos = MSSystem.PrevPos;

            // Establecemos los parametros del efecto.
            Effect.View = Camera.View;
            Effect.Projection = Camera.Projection;
            Effect.World = Matrix.Identity;
            Effect.LightingEnabled = false;
            Effect.VertexColorEnabled = true;
            Device.RenderState.PointSize = 3;

            

            Integrator.Initialize();
            base.Initialize();
        } 
        #endregion

        #region Load Content
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            SpriteBatch = new SpriteBatch(GraphicsDevice);
        } 
        #endregion


        #region Update
        /// <summary>
        /// TODO.
        /// </summary>
        /// <param name="gameTime">Cuanto tiempo ha pasado desde la ultima llamada.</param>
        protected override void Update(GameTime gameTime)
        {
            Integrator.Integrate((float)gameTime.ElapsedGameTime.TotalSeconds);
            base.Update(gameTime);
        } 
        #endregion

        #region Draw
        /// <summary>
        /// Dibujamos la simulacion.
        /// </summary>
        /// <param name="gameTime">Cuanto tiempo ha pasado desde la ultima llamada.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(new Color(40, 40, 40));


            for (int i = 0; i < Settings.ParticleCount; i++)
            {
                Float4 pos = MSSystem.GetPos(i);
                Vertices[i].Position = new Float3() { x = pos.x, y = pos.y, z = pos.z };
            }

            Effect.Begin();
            Effect.CurrentTechnique.Passes[0].Begin();
            {
                Device.VertexDeclaration = new VertexDeclaration(Device, CUDAVertexPositionColor.VertexElements);
                Device.DrawUserPrimitives<CUDAVertexPositionColor>(PrimitiveType.PointList, Vertices, 0, Settings.ParticleCount);
            }
            Effect.CurrentTechnique.Passes[0].End();
            Effect.End();
            base.Draw(gameTime);
        } 
        #endregion
    }
}
